home *** CD-ROM | disk | FTP | other *** search
/ Ham Radio 2000 #2 / Ham Radio 2000 - Volume 2.iso / HAMV2 / PACKET / TFLINK10 / TFLNKSRC / IBMCOM.C < prev    next >
Encoding:
C/C++ Source or Header  |  1996-04-14  |  21.5 KB  |  501 lines

  1. /*****************************************************************************
  2.  *                   ibmcom.c                     *
  3.  *****************************************************************************
  4.  * DESCRIPTION:    This file contains a set of routines for doing low-level     *
  5.  *        serial communications on the IBM PC.  It was translated         *
  6.  *        directly from Wayne Conrad's IBMCOM.PAS version 3.1, with    *
  7.  *        the goal of near-perfect functional correspondence between   *
  8.  *        the Pascal and C versions.                     *
  9.  *                                         *
  10.  * REVISIONS:    18 OCT 89 - RAC - Original translation from IBMCOM.PAS, with *
  11.  *                  liberal plagiarism of comments from the    *
  12.  *                  Pascal.                     *
  13.  *****************************************************************************/
  14.  
  15. #include    <stdio.h>
  16. #include    <dos.h>
  17. #include    "ibmcom.h"
  18. #include    "tflink.h"    /* for redefinition of setvect/getvect */
  19.  
  20. /*****************************************************************************
  21.  *                   8250 Definitions                     *
  22.  *****************************************************************************/
  23.  
  24. /*      Offsets to various 8250 registers.  Taken from IBM Technical         */
  25. /*      Reference Manual, p. 1-225                                           */
  26.  
  27. #define TXBUFF  0                       /* Transmit buffer register */
  28. #define RXBUFF  0                       /* Receive buffer register */
  29. #define DLLSB   0                       /* Divisor latch LS byte */
  30. #define DLMSB   1                       /* Divisor latch MS byte */
  31. #define IER     1                       /* Interrupt enable register */
  32. #define IIR     2                       /* Interrupt ID register */
  33. #define LCR     3                       /* Line control register */
  34. #define MCR     4                       /* Modem control register */
  35. #define LSR     5                       /* Line status register */
  36. #define MSR     6                       /* Modem status register */
  37.  
  38. /*      Modem control register bits                                          */
  39.  
  40. #define DTR     0x01                    /* Data terminal ready */
  41. #define RTS     0x02                    /* Request to send */
  42. #define OUT1    0x04                    /* Output #1 */
  43. #define OUT2    0x08                    /* Output #2 */
  44. #define LPBK    0x10                    /* Loopback mode bit */
  45.  
  46. /*      Modem status register bits                                           */
  47.  
  48. #define DCTS    0x01                    /* Delta clear to send */
  49. #define DDSR    0x02                    /* Delta data set ready */
  50. #define TERI    0x04                    /* Trailing edge ring indicator */
  51. #define DRLSD   0x08                    /* Delta Rx line signal detect */
  52. #define CTS     0x10                    /* Clear to send */
  53. #define DSR     0x20                    /* Data set ready */
  54. #define RI      0x40                    /* Ring indicator */
  55. #define RLSD    0x80                    /* Receive line signal detect */
  56.  
  57. /*      Line control register bits                                           */
  58.  
  59. #define DATA5   0x00                    /* 5 Data bits */
  60. #define DATA6   0x01                    /* 6 Data bits */
  61. #define DATA7   0x02                    /* 7 Data bits */
  62. #define DATA8   0x03                    /* 8 Data bits */
  63.  
  64. #define STOP1   0x00                    /* 1 Stop bit */
  65. #define STOP2   0x04                    /* 2 Stop bits */
  66.  
  67. #define NOPAR   0x00                    /* No parity */
  68. #define ODDPAR  0x08                    /* Odd parity */
  69. #define EVNPAR  0x18                    /* Even parity */
  70. #define STKPAR  0x28                    /* Stick parity */
  71. #define ZROPAR    0x38            /* Zero parity */
  72.  
  73. /*      Line status register bits                                            */
  74.  
  75. #define RDR     0x01                    /* Receive data ready */
  76. #define ERRS    0x1E                    /* All the error bits */
  77. #define TXR     0x20                    /* Transmitter ready */
  78.  
  79. /*      Interrupt enable register bits                                       */
  80.  
  81. #define DR      0x01                    /* Data ready */
  82. #define THRE    0x02                    /* Tx buffer empty */
  83. #define RLS     0x04                    /* Receive line status */
  84.  
  85. /*****************************************************************************
  86.  *                   Names for Numbers                 *
  87.  *****************************************************************************/
  88.  
  89. #define MAX_PORT    4
  90.  
  91. #define TRUE        1
  92. #define FALSE        0
  93.  
  94. /*****************************************************************************
  95.  *                  Global Data                     *
  96.  *****************************************************************************/
  97.  
  98. /*  UART i/o addresses.  Values depend upon which COMM port is selected  */
  99.  
  100. int    uart_data;        /* Data register */
  101. int    uart_ier;        /* Interrupt enable register */
  102. int    uart_iir;        /* Interrupt identification register */
  103. int    uart_lcr;        /* Line control register */
  104. int    uart_mcr;        /* Modem control register */
  105. int    uart_lsr;        /* Line status register */
  106. int    uart_msr;        /* Modem status register */
  107.  
  108. char    com_installed;        /* Flag: Communications routines installed */
  109. int    intnum;            /* Interrupt vector number for chosen port */
  110. char    i8259bit;        /* 8259 bit mask */
  111. char    old_i8259_mask;        /* Copy as it was when we were called */
  112. char    old_ier;        /* Modem register contents saved for */
  113. char    old_mcr;        /*  restoring when we're done */
  114. void interrupt (*old_vector)();    /* Place to save COM1 vector */
  115.  
  116. /*  Transmit queue.  Characters to be transmitted are held here until the  */
  117. /*  UART is ready to transmit them.  */
  118.  
  119. #define TX_QUEUE_SIZE    16    /* Transmit queue size.  Change to suit */
  120.  
  121. char    tx_queue[TX_QUEUE_SIZE];
  122. int    tx_in;            /* Index of where to store next character */
  123. int    tx_out;            /* Index of where to retrieve next character */
  124. int    tx_chars;        /* Count of characters in queue */
  125.  
  126. /*  Receive queue.  Received characters are held here until retrieved by  */
  127. /*  com_rx()  */
  128.  
  129. #define RX_QUEUE_SIZE    4096    /* Receive queue size.  Change to suit */
  130.  
  131. char    rx_queue[RX_QUEUE_SIZE];
  132. int    rx_in;            /* Index of where to store next character */
  133. int    rx_out;            /* Index of where to retrieve next character */
  134. int    rx_chars;        /* Count of characters in queue */
  135.  
  136. /*****************************************************************************
  137.  *                 com_install()                     *
  138.  *****************************************************************************
  139.  * DESCRIPTION:    Installs the communications drivers.                 *
  140.  *                                         *
  141.  * SYNOPSIS:    status = com_install(int portnum);                 *
  142.  *        int    portnum;    Desired port number             *
  143.  *        int    status;        0 = Successful installation         *
  144.  *                    1 = Invalid port number                *
  145.  *                    2 = No UART for specified port         *
  146.  *                    3 = Drivers already installed         *
  147.  *                                         *
  148.  * REVISIONS:    18 OCT 89 - RAC - Translated from IBMCOM.PAS             *
  149.  *****************************************************************************/
  150.  
  151. const int    uart_base[] =    { 0x3F8, 0x2F8, 0x3E8, 0x2E8 };
  152. const char    intnums[] =    { 0x0C,  0x0B,  0x0C,  0x0B };
  153. const char    i8259levels[] =    { 4,     3,     4,     3 };
  154.  
  155. int com_install(int portnum) {
  156.  
  157.     if (com_installed)                /* Drivers already installed */
  158.     return 3;
  159.     if ((portnum < 1) || (portnum > MAX_PORT))    /* Port number out of bounds */
  160.     return 1;
  161.  
  162.     uart_data = uart_base[portnum-1];        /* Set UART I/O addresses */
  163.     uart_ier  = uart_data + IER;        /*  for the selected comm */
  164.     uart_iir  = uart_data + IIR;        /*  port */
  165.     uart_lcr  = uart_data + LCR;
  166.     uart_mcr  = uart_data + MCR;
  167.     uart_lsr  = uart_data + LSR;
  168.     uart_msr  = uart_data + MSR;
  169.     intnum    = intnums[portnum-1];        /* Ditto for interrupt */
  170.     i8259bit  = 1 << i8259levels[portnum-1];    /*  vector and 8259 bit mask */
  171.  
  172.     old_ier = inportb(uart_ier);        /* Return an error if we */
  173.     outportb(uart_ier, 0);            /*  can't access the UART */
  174.     if (inportb(uart_ier) != 0)
  175.     return 2;
  176.  
  177.     disable();                    /* Save the original 8259 */
  178.     old_i8259_mask = inportb(0x21);        /*  mask, then disable the */
  179.     outportb(0x21, old_i8259_mask | i8259bit);    /*  8259 for this interrupt */
  180.     enable();
  181.  
  182.     com_flush_tx();                /* Clear the transmit and */
  183.     com_flush_rx();                /*  receive queues */
  184.  
  185.     old_vector = getvect(intnum);        /* Save old COMM vector, */
  186.     setvect(intnum, &com_interrupt_driver);    /*  then install a new one, */
  187.     com_installed = TRUE;            /*  and note that we did */
  188.  
  189.     outportb(uart_lcr, DATA8 + NOPAR + STOP1);    /* 8 data, no parity, 1 stop */
  190.  
  191.     disable();                    /* Save MCR, then enable */
  192.     old_mcr = inportb(uart_mcr);        /*  interrupts onto the bus, */
  193.     outportb(uart_mcr,                /*  activate RTS and leave */
  194.          (old_mcr & DTR) | (OUT2 + RTS));    /*  DTR the way it was */
  195.     enable();
  196.  
  197.     outportb(uart_ier, DR);            /* Enable receive interrupts */
  198.  
  199.     disable();                    /* Now enable the 8259 for */
  200.     outportb(0x21, inportb(0x21) & ~i8259bit);    /*  this interrupt */
  201.     enable();
  202.     return 0;                    /* Successful installation */
  203.     }                        /* End com_install() */
  204.  
  205. /*****************************************************************************
  206.  *                 com_install()                     *
  207.  *****************************************************************************
  208.  * DESCRIPTION:    Denstalls the communications drivers completely, without     *
  209.  *        changing the baud rate or DTR.  It tries to leave the        *
  210.  *        interrupt vectors and enables and everything else as they    *
  211.  *        were when the driver was installed.                 *
  212.  *                                         *
  213.  * NOTE:    This function MUST be called before returning to DOS, so the *
  214.  *        interrupt vector won't point to our driver anymore, since it *
  215.  *        will surely get overwritten by some other transient program  *
  216.  *        eventually.                             *
  217.  *                                         *
  218.  * REVISIONS:    18 OCT 89 - RAC - Translated from IBMCOM.PAS             *
  219.  *****************************************************************************/
  220.  
  221. void com_deinstall(void) {
  222.  
  223.     if (com_installed) {            /* Don't de-install twice! */
  224.     outportb(uart_mcr, old_mcr);        /* Restore the UART */
  225.     outportb(uart_ier, old_ier);        /*  registers ... */
  226.     disable();
  227.     outportb(0x21,                /*  ... the 8259 interrupt */
  228.          (inportb(0x21)  & ~i8259bit) | /*  mask ... */
  229.          (old_i8259_mask &  i8259bit));
  230.     enable();
  231.     setvect(intnum, old_vector);        /*  ... and the comm */
  232.     com_installed = FALSE;            /*  interrupt vector */
  233.     }                    /* End com_installed */
  234.     }                        /* End com_deinstall() */
  235.  
  236. /*****************************************************************************
  237.  *                com_set_speed()                     *
  238.  *****************************************************************************
  239.  * DESCRIPTION:    Sets the baud rate.                         *
  240.  *                                         *
  241.  * SYNOPSIS:    void com_set_speed(unsigned speed);                 *
  242.  *        unsigned speed;            Desired baud rate         *
  243.  *                                         *
  244.  * NOTES:    The input parameter can be anything between 2 and 65535.     *
  245.  *        However, I (Wayne) am not sure that extremely high speeds    *
  246.  *        (those above 19200) will always work, since the baud rate    *
  247.  *        divisor will be six or less, where a difference of one can   *
  248.  *        represent a difference in baud rate of 3840 bits per second  *
  249.  *        or more.)                             *
  250.  *                                         *
  251.  * REVISIONS:    18 OCT 89 - RAC - Translated from IBMCOM.PAS             *
  252.  *****************************************************************************/
  253.  
  254. void com_set_speed(unsigned speed) {
  255.  
  256.     unsigned    divisor;            /* A local temp */
  257.  
  258.     if (com_installed) {
  259.     if (speed < 2) speed = 2;        /* Force proper input */
  260.     divisor = 115200L / speed;        /* Recond baud rate divisor */
  261.     disable();                /* Interrupts off */
  262.     outportb(uart_lcr,            /* Set up to load baud rate */
  263.          inportb(uart_lcr) | 0x80);    /*  divisor into UART */
  264.     outport(uart_data, divisor);        /* Do so */
  265.     outportb(uart_lcr,            /* Back to normal UART ops */
  266.          inportb(uart_lcr) & ~0x80);
  267.     enable();                /* Interrupts back on */
  268.     }                    /* End "comm installed" */
  269.     }                        /* End com_set_speed() */
  270.  
  271. /*****************************************************************************
  272.  *                   com_set_parity()                     *
  273.  *****************************************************************************
  274.  * DESCRIPTION: Sets the parity and stop bits.                     *
  275.  *                                         *
  276.  * SYNOPSIS:    void com_set_parity(enum par_code parity, int stop_bits);    *
  277.  *        int    code;        COM_NONE = 8 data bits, no parity    *
  278.  *                    COM_EVEN = 7 data, even parity         *
  279.  *                    COM_ODD  = 7 data, odd parity         *
  280.  *                    COM_ZERO = 7 data, parity bit = zero *
  281.  *                    COM_ONE  = 7 data, parity bit = one  *
  282.  *        int    stop_bits;    Must be 1 or 2                 *
  283.  *                                         *
  284.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal             *
  285.  *****************************************************************************/
  286.  
  287. const char    lcr_vals[] = {
  288.             DATA8 + NOPAR,
  289.             DATA7 + EVNPAR,
  290.             DATA7 + ODDPAR,
  291.             DATA7 + STKPAR,
  292.             DATA7 + ZROPAR
  293.             } ;
  294.  
  295. void com_set_parity(enum par_code parity, int stop_bits) {
  296.     disable();
  297.     outportb(uart_lcr, lcr_vals[parity] | ((stop_bits == 2) ? STOP2 : STOP1));
  298.     enable();    
  299.     }                        /* End com_set_parity() */
  300.  
  301. /*****************************************************************************
  302.  *                com_raise_dtr()                     *
  303.  *                com_lower_dtr()                     *
  304.  *****************************************************************************
  305.  * DESCRIPTION:    These routines raise and lower the DTR line.  Lowering DTR   *
  306.  *        causes most modems to hang up.                     *
  307.  *                                         *
  308.  * REVISIONS:    18 OCT 89 - RAC - Transltated from the Pascal.             *
  309.  *****************************************************************************/
  310.  
  311. void com_lower_dtr(void) {
  312.     if (com_installed) {
  313.         disable();
  314.     outportb(uart_mcr, inportb(uart_mcr) & ~DTR);
  315.         enable();
  316.     }                    /* End 'comm installed' */
  317.     }                        /* End com_raise_dtr() */
  318.  
  319. void com_raise_dtr(void) {
  320.     if (com_installed) {
  321.         disable();
  322.     outportb(uart_mcr, inportb(uart_mcr) | DTR);
  323.         enable();
  324.     }                    /* End 'comm installed' */
  325.     }                        /* End com_lower_dtr() */
  326.  
  327. /*****************************************************************************
  328.  *                   com_tx()                     *
  329.  *                com_tx_string()                     *
  330.  *****************************************************************************
  331.  * DESCRIPTION: Transmit routines.  com_tx() sends a single character by     *
  332.  *        waiting until the transmit buffer isn't full, then putting   *
  333.  *        the character into it.  The interrupt driver will then send  *
  334.  *        the character once it is at the head of the transmit queue   *
  335.  *        and a transmit interrupt occurs.  com_tx_string() sends a    *
  336.  *        string by repeatedly calling com_tx().                 *
  337.  *                                         *
  338.  * SYNOPSES:    void    com_tx(char c);        Send the character c         *
  339.  *        void    com_tx_string(char *s);    Send the string s         *
  340.  *                                         *
  341.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal             *
  342.  *****************************************************************************/
  343.  
  344. void com_tx(char c) {
  345.     if (com_installed) {
  346.     while (!com_tx_ready()) ;        /* Wait for non-full buffer */
  347.     disable();                /* Interrupts off */
  348.     tx_queue[tx_in++] = c;            /* Stuff character in queue */
  349.     if (tx_in == TX_QUEUE_SIZE) tx_in = 0;    /* Wrap index if needed */
  350.     tx_chars++;                /* Number of char's in queue */
  351.     outportb(uart_ier,            /* Enable UART tx interrupt */
  352.          inportb(uart_ier) | THRE);
  353.     enable();                /* Interrupts back on */
  354.     }                    /* End 'comm installed' */
  355.     }                        /* End com_tx() */
  356.  
  357. void com_tx_string(char *s) {
  358.     while (*s) com_tx(*s++);            /* Send the string! */
  359.     }                        /* End com_tx_string() */
  360.  
  361. /*****************************************************************************
  362.  *                   com_rx()                     *
  363.  *****************************************************************************
  364.  * DESCRIPTION:    Returns the next character from the receive buffer, or a     *
  365.  *        NULL character ('\0') if the buffer is empty.             *
  366.  *                                         *
  367.  * SYNOPSIS:    c = com_rx();                             *
  368.  *        char    c;            The returned character         *
  369.  *                                         *
  370.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal.             *
  371.  *****************************************************************************/
  372.  
  373. char    com_rx(void) {
  374.  
  375.     char    rv;                /* Local temp */
  376.  
  377.     if (!rx_chars || !com_installed)        /* Return NULL if receive */
  378.     return '\0';                /*  buffer is empty */
  379.     disable();                    /* Interrupts off */
  380.     rv = rx_queue[rx_out++];            /* Grab char from queue */
  381.     if (rx_out == RX_QUEUE_SIZE)        /* Wrap index if needed */
  382.     rx_out = 0;
  383.     rx_chars--;                    /* One less char in queue */
  384.     enable();                    /* Interrupts back on */
  385.     return rv;                    /* The answer! */
  386.     }                        /* End com_rx() */
  387.  
  388. /*****************************************************************************
  389.  *                 Queue Status Routines                 *
  390.  *****************************************************************************
  391.  * DESCRIPTION:    Small routines to return status of the transmit and receive  *
  392.  *        queues.                                 *
  393.  *                                         *
  394.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal.             *
  395.  *****************************************************************************/
  396.  
  397. int com_tx_ready(void) {            /* Return TRUE if the */
  398.     return ((tx_chars < TX_QUEUE_SIZE) ||    /*  transmit queue can */
  399.         (!com_installed));            /*  accept a character */
  400.     }                        /* End com_tx_ready() */
  401.  
  402. int com_tx_empty(void) {            /* Return TRUE if the */
  403.     return (!tx_chars || (!com_installed));    /*  transmit queue is empty */
  404.     }                        /* End com_tx_empty() */
  405.  
  406. int com_rx_empty(void) {            /* Return TRUE if the */
  407.     return (!rx_chars || (!com_installed));    /*  receive queue is empty */
  408.     }                        /* End com_tx_empty() */
  409.  
  410. /*****************************************************************************
  411.  *                com_flush_tx()                     *
  412.  *                com_flush_rx()                     *
  413.  *****************************************************************************
  414.  * DESCRIPTION:    Buffer flushers!  These guys just initialize the transmit    *
  415.  *        and receive queues (respectively) to their empty state.         *
  416.  *                                         *
  417.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal             *
  418.  *****************************************************************************/
  419.  
  420. void com_flush_tx() { disable(); tx_chars = tx_in = tx_out = 0; enable(); }
  421. void com_flush_rx() { disable(); rx_chars = rx_in = rx_out = 0; enable(); }
  422.  
  423. /*****************************************************************************
  424.  *                 com_carrier()                     *
  425.  *****************************************************************************
  426.  * DESCRIPTION:    Returns TRUE if a carrier is present.                 *
  427.  *                                         *
  428.  * REVISIONS:    18 OCT 89 - RAC - Translated from the Pascal.             *
  429.  *****************************************************************************/
  430.  
  431. int com_carrier(void) {
  432.     return com_installed && (inportb(uart_msr) & RLSD);
  433.     }                        /* End com_carrier() */
  434.  
  435. /*****************************************************************************
  436.  *                com_interrupt_driver()                 *
  437.  *****************************************************************************
  438.  * DESCRIPTION:    Handles communications interrupts.  The UART will interrupt  *
  439.  *        whenever a character has been received or when it is ready   *
  440.  *        to transmit another character.  This routine responds by     *
  441.  *        sticking received characters into the receive queue and      *
  442.  *        yanking characters to be transmitted from the transmit queue *
  443.  *                                         *
  444.  * REVISIOSN:    18 OCT 89 - RAC - Translated from the Pascal.             *
  445.  *****************************************************************************/
  446.  
  447. void interrupt com_interrupt_driver() {
  448.  
  449.     char    iir;                /* Local copy if IIR */
  450.     char    c;                /* Local character variable */
  451.  
  452. /*  While bit 0 of the IIR is 0, there remains an interrupt to process  */
  453.  
  454.     while (!((iir = inportb(uart_iir)) & 1)) {    /* While there is an int ... */
  455.     switch (iir) {                /* Branch on interrupt type */
  456.  
  457.         case 0:                /* Modem status interrupt */
  458.         inportb(uart_msr);        /* Just clear the interrupt */
  459.         break;
  460.  
  461.         case 2:                /* Transmit register empty */
  462.  
  463. /*****************************************************************************
  464.  *  NOTE:  The test of the line status register is to see if the transmit    *
  465.  *       holding register is truly empty.  Some UARTS seem to cause         *
  466.  *       transmit interrupts when the holding register isn't empty,         *
  467.  *       causing transmitted characters to be lost.                 *
  468.  *****************************************************************************/
  469.  
  470.         if (tx_chars <= 0)        /* If tx buffer empty, turn */
  471.             outportb(uart_ier,        /*  off transmit interrupts */
  472.                  inportb(uart_ier) & ~2);
  473.         else {                /* Tx buffer not empty */
  474.             if (inportb(uart_lsr) & TXR) {
  475.             outportb(uart_data, tx_queue[tx_out++]);
  476.             if (tx_out == TX_QUEUE_SIZE)
  477.                 tx_out = 0;
  478.             tx_chars--;
  479.             }
  480.             }                /* End 'tx buffer not empty */
  481.         break;
  482.  
  483.         case 4:                /* Received data interrupt */
  484.         c = inportb(uart_data);        /* Grab received character */
  485.         if (rx_chars < RX_QUEUE_SIZE) { /* If queue not full, save */
  486.             rx_queue[rx_in++] = c;    /*  the new character */
  487.             if (rx_in == RX_QUEUE_SIZE) /* Wrap index if needed */
  488.             rx_in = 0;
  489.             rx_chars++;            /* Count the new character */
  490.             }                /* End queue not full */
  491.         break;
  492.  
  493.         case 6:                /* Line status interrupt */
  494.         inportb(uart_lsr);        /* Just clear the interrupt */
  495.         break;
  496.  
  497.         }                    /* End switch */
  498.     }                    /* End 'is an interrupt' */
  499.     outportb(0x20, 0x20);            /* Send EOI to 8259 */
  500.     }                        /* End com_interrupt_driver() */
  501.